#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.IPCQueues;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.IPCQueues
{
    [TestFixture]
    public sealed class GuardedEnumeratorFixture : AssertionHelper
    {
        [Test(Description = "The current item of a guarded enumerator is initially null.")]
        public void Current_IsInitiallyNull()
        {
            var guard = new GuardedEnumerator<object>(new Mock<ISemaphore>().Object, new Mock<ISemaphore>().Object,
                                                      new Mock<IEnumerator<object>>().Object);
            var result = guard.Current;

            Expect(result, Null, "Current item was not null.");
        }

        [Test(Description = "Moving to the next item waits on the guard.")]
        public void MoveNext_WaitsOnGuard()
        {
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));

            var enumerator = new GuardedEnumerator<object>(guard.Object, new Mock<ISemaphore>().Object,
                                                      new Mock<IEnumerator<object>>().Object);
            enumerator.MoveNext(TimeSpan.FromMinutes(1));

            guard.Verify(s => s.Wait(It.IsAny<TimeSpan>()),"MoveNext() did not wait on the guard.");
        }

        [Test(Description = "The completion of moving to the next item advances the underlying enumerator.")]
        public void MoveNext_AdvancesEnumeratorOnCompletion()
        {
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            
            var guarded = new GuardedEnumerator<object>(guard.Object, new Mock<ISemaphore>().Object,
                                                      enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();

            enumerator.Verify(e => e.MoveNext(), "Underlying enumerator was not advanced.");
        }

        [Test(Description = "The completions of moving to the next item obtains the current value of " +
                            "the underlying enumerator.")]
        public void MoveNext_ObtainsCurrentFromEnumerator()
        {
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            enumerator.Setup(e => e.MoveNext()).Returns(true);
            
            var guarded = new GuardedEnumerator<object>(guard.Object, new Mock<ISemaphore>().Object,
                                                      enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();
            
            enumerator.VerifyGet(e => e.Current, "Current value of the enumerator was not retreived.");
        }

        [Test(Description = "The completion of moving to the next item sets the current item.")]
        public void MoveNext_SetCurrent()
        {
            var newitem = new object();
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            enumerator.Setup(e => e.MoveNext()).Returns(true);
            enumerator.SetupGet(e => e.Current).Returns(newitem);
            
            var guarded = new GuardedEnumerator<object>(guard.Object, new Mock<ISemaphore>().Object,
                                                      enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();

            Expect(guarded.Current, EqualTo(newitem), "Current not set to expected item.");
        }

        [Test(Description = "The completion of moving to the next item releases the signal semaphore if " +
                            "the current item had been previously set.")]
        public void MoveNext_ReleasesSignalSemaphoreIfCurrentNotNull()
        {
            var newitem = new object();
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            enumerator.Setup(e => e.MoveNext()).Returns(true);
            enumerator.SetupGet(e => e.Current).Returns(newitem);
            var signal = new Mock<ISemaphore>();

            var guarded = new GuardedEnumerator<object>(guard.Object, signal.Object,
                                                      enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();
            
            signal.Verify(s => s.Release(), "Signal semaphore was not released");
        }


        [Test(Description = "Flushing the guarded enumerator relases the signal semaphore.")]
        public void Flush_ReleasesSignalSemaphore()
        {
            var newitem = new object();
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            enumerator.Setup(e => e.MoveNext()).Returns(true);
            enumerator.SetupGet(e => e.Current).Returns(newitem);
            var signal = new Mock<ISemaphore>();

            var guarded = new GuardedEnumerator<object>(guard.Object, signal.Object,
                                                      enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();
            guarded.Flush();

            signal.Verify(s => s.Release(), "Signal semaphore not released.");
        }

        [Test(Description = "Flushing the guarded enumerator does not release the signal semaphore if the " +
                            "current item was not previously set.")]
        public void Flush_DoesNoteReleaseSignalIfCurrentSetToNull()
        {
            var signal = new Mock<ISemaphore>();

            var guarded = new GuardedEnumerator<object>(new Mock<ISemaphore>().Object, signal.Object,
                                                        new Mock<IEnumerator<object>>().Object);
            guarded.Flush();
            
            signal.Verify(s => s.Release(), Times.Never(), "Signal semaphore was unexpectedly released.");
        }

        [Test(Description = "Flushing the guared enumerator sets the current item to null.")]
        public void Flush_SetsCurrentToNull()
        {
            var newitem = new object();
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            enumerator.Setup(e => e.MoveNext()).Returns(true);
            enumerator.SetupGet(e => e.Current).Returns(newitem);

            var guarded = new GuardedEnumerator<object>(guard.Object, new Mock<ISemaphore>().Object,
                                                        enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();
            guarded.Flush();

            Expect(guarded.Current, Null, "Current not set to null.");
        }

        [Test(Description = "Ending the enumeration releases the signal semaphore if the current item was " +
                            "previously set.")]
        public void EndEnumeration_ReleasesSignalSemaphore()
        {
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var enumerator = new Mock<IEnumerator<object>>();
            enumerator.Setup(e => e.MoveNext()).Returns(true);
            enumerator.SetupGet(e => e.Current).Returns(new object());
            var signal = new Mock<ISemaphore>();

            var guarded = new GuardedEnumerator<object>(guard.Object, signal.Object,
                                                      enumerator.Object);
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();
            guarded.EndEnumeration();

            signal.Verify(s => s.Release(), "Signal semaphore not released.");
        }

        [Test(Description = "Ending the enumeration prevents future attempts to move to the next item from " +
                            "waiting on the guard.")]
        public void EndEnumeration_PreventsMoveNextFromWaitingOnGuard()
        {
            var guard = new Mock<ISemaphore>();
            guard.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));

            var guarded = new GuardedEnumerator<object>(guard.Object, new Mock<ISemaphore>().Object,
                                                        new Mock<IEnumerator<object>>().Object);
            guarded.EndEnumeration();
            guarded.MoveNext(TimeSpan.FromMinutes(1)).Wait();

            guard.Verify(g => g.Wait(It.IsAny<TimeSpan>()), Times.Never(),
                         "Moving to the next item waited on the guard unexpectedly.");
        }
    }
}